package de.uni_bremen.exceptions.oops;

import java.util.LinkedList;

/**
 * Die Klasse repräsentiert einen Ausdruck im Syntaxbaum, der dem Zugriff auf eine Variable oder ein Attribut bzw. einem
 * Methodenaufruf entspricht.
 */
class VarOrCall extends Expression
{
	/** Der Name des Attributs, der Variablen oder der Methode. */
	ResolvableIdentifier	identifier;
	
	/** Liste der übergegeben Parameter */
	LinkedList<Expression>	params	= new LinkedList<Expression>();
	
	/**
	 * Konstruktor.
	 * 
	 * @param identifier
	 *            Der Name des Attributs, der Variablen oder der Methode.
	 */
	VarOrCall(ResolvableIdentifier identifier)
	{
		super(identifier.position);
		this.identifier = identifier;
	}
	
	/**
	 * Die Methode führt die Kontextanalyse für diesen Ausdruck durch. Dabei wird ein Zugriff über SELF in den
	 * Syntaxbaum eingefügt, wenn dieser Ausdruck ein Attribut oder eine Methode bezeichnet. Diese Methode wird niemals
	 * für Ausdrücke aufgerufen, die rechts vom Objekt-Zugriffsoperator stehen.
	 * 
	 * @param declarations
	 *            Die an dieser Stelle gültigen Deklarationen.
	 * @return Dieser Ausdruck oder ein neuer Ausdruck, falls ein Boxing oder der Zugriff über SELF eingefügt wurde.
	 * @throws CompileException
	 *             Während der Kontextanylyse wurde ein Fehler gefunden.
	 */
	Expression contextAnalysis(Declarations declarations) throws CompileException
	{
		contextAnalysisForMember(declarations, null);
		if (identifier.declaration instanceof MethodDeclaration || identifier.declaration instanceof VarDeclaration
				&& ((VarDeclaration) identifier.declaration).isAttribute)
		{
			AccessExpression a = new AccessExpression(new VarOrCall(new ResolvableIdentifier("_self", position)), this);
			a.leftOperand = a.leftOperand.contextAnalysis(declarations);
			a.leftOperand = a.leftOperand.box(declarations);
			a.type = type;
			a.lValue = lValue;
			return a;
		}
		else
		{
			return this;
		}
	}
	
	/**
	 * Die Methode führt die Kontextanalyse für diesen Ausdruck durch. Diese Methode wird auch für Ausdrücke aufgerufen,
	 * die rechts vom Objekt-Zugriffsoperator stehen.
	 * 
	 * @param declarations
	 *            Die an dieser Stelle gültigen Deklarationen.
	 * @param baseDeclarations
	 *            Die an dieser Stelle gültigen Deklarationen für die Vererbung.
	 * @throws CompileException
	 *             Während der Kontextanylyse wurde ein Fehler gefunden.
	 */
	void contextAnalysisForMember(Declarations declarations, Declarations baseDeclarations) throws CompileException
	{
		if (baseDeclarations == null)
			baseDeclarations = declarations;
		declarations.resolveVarOrMethod(identifier);
		if (identifier.declaration instanceof VarDeclaration)
		{
			// Zugriff prüfen
			if (declarations.currentClass.attributes.containsKey(identifier.declaration))
				switch (declarations.currentClass.attributes.get(identifier.declaration))
				{
					case PRIVATE:
						if (!declarations.currentClass.equals(baseDeclarations.currentClass))
							throw new CompileException("Zugriff auf PRIVATE-Attribut hier nicht möglich", position);
						break;
					case PROTECTED:
						if (!declarations.currentClass.isA(baseDeclarations.currentClass))
							throw new CompileException("Zugriff auf PROTECTED-Attribut hier nicht möglich", position);
						break;
				}
			
			type = (ClassDeclaration) ((VarDeclaration) identifier.declaration).type.declaration;
			// if (!((VarDeclaration) identifier.declaration).isParameter)
			lValue = true;
		}
		else
			if (identifier.declaration instanceof MethodDeclaration)
			{
				// Zugriff prüfen
				if (declarations.currentClass.methods.containsKey(identifier.declaration))
					switch (declarations.currentClass.methods.get(identifier.declaration))
					{
						case PRIVATE:
							if (!declarations.currentClass.equals(baseDeclarations.currentClass))
								throw new CompileException("Zugriff auf PRIVATE-Methode hier nicht möglich", position);
							break;
						case PROTECTED:
							if (!declarations.currentClass.isA(baseDeclarations.currentClass))
								throw new CompileException("Zugriff auf PROTECTED-Methode hier nicht möglich", position);
							break;
					}
				
				MethodDeclaration m = (MethodDeclaration) identifier.declaration;
				if (params.size() != m.params.size())
					throw new CompileException("Parameter-Anzahl stimmt nicht mit deklarierter Anzahl überein",
							position);
				int i = 0;
				for (Expression e : params)
				{
					e.contextAnalysis(baseDeclarations);
					e = e.box(baseDeclarations);
					params.set(i, e);
					VarDeclaration v = m.params.get(i++);
					baseDeclarations.resolveType(v.type);
					if (!((ClassDeclaration) v.type.declaration).isA(e.type))
						throw new CompileException("Parameter-Typ stimmt nicht mit deklariertem Typ überein",
								e.position);
					
				}
				if (m.type == null)
					type = ClassDeclaration.voidType;
				else
					type = (ClassDeclaration) m.type.declaration;
			}
			else
			{
				assert false;
			}
	}
	
	/**
	 * Die Methode gibt diesen Ausdruck in einer Baumstruktur aus. Wenn der Typ des Ausdrucks bereits ermittelt wurde,
	 * wird er auch ausgegeben.
	 * 
	 * @param tree
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void print(TreeStream tree)
	{
		tree.println(identifier.name + (type == null ? "" : " : " + (lValue ? "REF " : "") + type.identifier.name));
	}
	
	/**
	 * Die Methode generiert den Assembler-Code für diesen Ausdruck. Sie geht davon aus, dass die Kontextanalyse vorher
	 * erfolgreich abgeschlossen wurde.
	 * 
	 * @param code
	 *            Der Strom, in den die Ausgabe erfolgt.
	 */
	void generateCode(CodeStream code)
	{
		if (identifier.declaration instanceof VarDeclaration)
		{
			VarDeclaration v = (VarDeclaration) identifier.declaration;
			if (v.isAttribute)
			{
				code.println("; Referenz auf Attribut " + identifier.name);
				code.println("MRM R5, (R2)");
				code.println("MRI R6, " + v.offset);
				code.println("ADD R5, R6");
				code.println("MMR (R2), R5");
			}
			else
			{
				if (v.isParameter)
					code.println("; Referenz auf Parameter " + identifier.name);
				else
					code.println("; Referenz auf Variable " + identifier.name);
				code.println("MRI R5, " + v.offset);
				code.println("ADD R5, R3");
				code.println("ADD R2, R1");
				code.println("MMR (R2), R5");
			}
		}
		else
			if (identifier.declaration instanceof MethodDeclaration)
			{
				MethodDeclaration m = (MethodDeclaration) identifier.declaration;
				
				for (Expression e : params)
				{
					e.generateCode(code);
				}
				
				String returnLabel = code.nextLabel();
				code.println("MRI R5, " + returnLabel);
				code.println("ADD R2, R1");
				code.println("MMR (R2), R5 ; Rücksprungadresse auf den Stapel");
				code.println("; Statischer Aufruf von " + identifier.name);
				code.println("MRI R0, " + m.self.type.name + "_" + m.identifier.name);
				code.println(returnLabel + ":");
			}
			else
			{
				assert false;
			}
	}
	
	/**
	 * Die Methode gibt diesen Ausdruck in Textform aus.
	 */
	@Override
	public String toString()
	{
		return "VARORCALL";
	}
}
